package payroll;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import payroll.exception.NoSuchEmployeeException;
import payroll.exception.NotCommissionedClassificationException;
import payroll.exception.NotHourlyClassificationException;
import payroll.trans.AddCommissionedEmployeeTransaction;
import payroll.trans.AddHourlyEmployeeTransaction;
import payroll.trans.AddSalariedEmployeeTransaction;
import payroll.trans.ChangeAddressTransaction;
import payroll.trans.ChangeCommissionedTransaction;
import payroll.trans.ChangeDirectTransaction;
import payroll.trans.ChangeHoldTransaction;
import payroll.trans.ChangeHourlyTransaction;
import payroll.trans.ChangeMailTransaction;
import payroll.trans.ChangeNameTransaction;
import payroll.trans.ChangeSalariedTransaction;
import payroll.trans.DeleteEmployeeTransaction;
import payroll.trans.PaydayTransaction;
import payroll.trans.SalesReceiptTransaction;
import payroll.trans.TimeCardTransaction;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.function.Consumer;

public class Main {

    private static Map<Pattern, Consumer<Arg>> cmds = new HashMap<>();

    public static void main(String[] args) {
        initCommands();

        System.out.println("====== Payroll ======");
        System.out.println("Input command or exit");
        System.out.println("---------------------");

        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("> ");
            String input = scanner.nextLine();
            if ("exit".equals(input)) {
                break;
            }

            boolean matched = false;
            for (Pattern pattern : cmds.keySet()) {
                Matcher matcher = pattern.matcher(input);
                if (matcher.matches()) {
                    matched = true;
                    try {
                        cmds.get(pattern).accept(new Arg(matcher));
                    } catch (NoSuchEmployeeException e) {
                        System.err.println("No such employee");
                    } catch (NotHourlyClassificationException e) {
                        System.err.println("Not hourly employee");
                    } catch (NotCommissionedClassificationException e) {
                        System.err.println("Not commissioned employee");
                    }
                    break;
                }
            }
            if (!matched)
                System.err.println("Command error");
        }
        scanner.close();
    }

    // space
    private static final String SP = "\\s+";
    // commands
    private static final String CMD_AddEmp = "AddEmp";
    private static final String CMD_DelEmp = "DelEmp";
    private static final String CMD_TimeCard = "TimeCard";
    private static final String CMD_SalesReceipt = "SalesReceipt";
    private static final String CMD_ChgEmp = "ChgEmp";
    private static final String CMD_Payday = "Payday";
    // regex of string, int or double var
    private static final String R_STR = "\"(?<var>.*)\"";
    private static final String R_INT = "(?<var>\\d+)";
    private static final String R_DBL = "(?<var>\\d+(\\.\\d+)?)";
    // regex of command arguments
    private static final String R_empId = R_INT.replace("var", "empId");
    private static final String R_name = R_STR.replace("var", "name");
    private static final String R_address = R_STR.replace("var", "address");
    private static final String R_hourlyRate = R_DBL.replace("var", "hourlyRate");
    private static final String R_salary = R_DBL.replace("var", "salary");
    private static final String R_commissionRate = R_DBL.replace("var", "commissionRate");
    private static final String R_date = "(?<date>\\d{4}-\\d{2}-\\d{2})";
    private static final String R_hours = R_DBL.replace("var", "hours");
    private static final String R_amount = R_DBL.replace("var", "amount");
    private static final String R_bank = R_STR.replace("var", "bank");
    private static final String R_account = R_STR.replace("var", "account");
 
    private static void initCommands() {
        cmds.put(Pattern.compile(String.join(SP, CMD_AddEmp, R_empId, R_name, R_address, "H", R_hourlyRate)), arg-> {
            new AddHourlyEmployeeTransaction(
                arg.getEmpId(), arg.getName(), arg.getAddress(), arg.getHourlyRate()
                ).execute();
                System.out.println("Hourly employee added");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_AddEmp, R_empId, R_name, R_address, "S", R_salary)), arg-> {
            new AddSalariedEmployeeTransaction(
                arg.getEmpId(), arg.getName(), arg.getAddress(), arg.getSalary()
                ).execute();
                System.out.println("Salaried employee added");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_AddEmp, R_empId, R_name, R_address, "C", R_salary, R_commissionRate)), arg-> {
            new AddCommissionedEmployeeTransaction(
                arg.getEmpId(), arg.getName(), arg.getAddress(), arg.getSalary(), arg.getCommissionRate()
                ).execute();
                System.out.println("Commissioned employee added");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_DelEmp, R_empId)), arg-> {
            new DeleteEmployeeTransaction(arg.getEmpId()).execute();
            System.out.println("Employee deleted");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_TimeCard, R_empId, R_date, R_hours)), arg-> {
            new TimeCardTransaction(arg.getEmpId(), arg.getDate(), arg.getHours()).execute();
            System.out.println("TimeCard added");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_SalesReceipt, R_empId, R_date, R_amount)), arg-> {
            new SalesReceiptTransaction(arg.getEmpId(), arg.getDate(), arg.getAmount()).execute();
            System.out.println("SalesReceipt added");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_ChgEmp, R_empId, "name", R_name)), arg-> {
            new ChangeNameTransaction(arg.getEmpId(), arg.getName()).execute();
            System.out.println("Name changed");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_ChgEmp, R_empId, "address", R_address)), arg-> {
            new ChangeAddressTransaction(arg.getEmpId(), arg.getAddress()).execute();
            System.out.println("Address changed");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_ChgEmp, R_empId, "hourly", R_hourlyRate)), arg-> {
            new ChangeHourlyTransaction(arg.getEmpId(), arg.getHourlyRate()).execute();
            System.out.println("Change to hourly");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_ChgEmp, R_empId, "salaried", R_salary)), arg-> {
            new ChangeSalariedTransaction(arg.getEmpId(), arg.getSalary()).execute();
            System.out.println("Change to salaried");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_ChgEmp, R_empId, "commissioned", R_salary, R_commissionRate)), arg-> {
            new ChangeCommissionedTransaction(arg.getEmpId(), arg.getSalary(), arg.getCommissionRate()).execute();
            System.out.println("Change to commissioned");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_ChgEmp, R_empId, "hold")), arg-> {
            new ChangeHoldTransaction(arg.getEmpId()).execute();
            System.out.println("Change to hold");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_ChgEmp, R_empId, "direct", R_bank, R_account)), arg-> {
            new ChangeDirectTransaction(arg.getEmpId(), arg.getBank(), arg.getAccount()).execute();
            System.out.println("Change to direct");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_ChgEmp, R_empId, "mail", R_address)), arg-> {
            new ChangeMailTransaction(arg.getEmpId(), arg.getAddress()).execute();
            System.out.println("Change to mail");
        });
        cmds.put(Pattern.compile(String.join(SP, CMD_Payday)), arg-> {
            PaydayTransaction t = new PaydayTransaction();
            t.execute();
            System.out.println("   EMPID   |         NAME       |   AMOUNT   |   DISPOSITION");
            System.out.println("-----------+--------------------+------------+--------------------------------------");
            for (Employee e : PayrollDatabase.listAllEmployees()) {
                Paycheck p = t.getPaycheckOf(e.getEmpId());
                System.out.printf("%10d | %-18s | %10.2f | %s\n", e.getEmpId(), e.getName(), p.getAmount(), p.getDisposition());
            }
            System.out.println("-----------+--------------------+------------+--------------------------------------");
        });
    }
    
    private static class Arg {
        private final Matcher matcher;
    
        Arg(Matcher matcher) {
            this.matcher = matcher;
        }
    
        String getStr(String name) {
            return matcher.group(name);
        }
        int getInt(String name) {
            return Integer.parseInt(matcher.group(name));
        }
        double getDbl(String name) {
            return Double.parseDouble(matcher.group(name));
        }
    
        int getEmpId() { return getInt("empId"); }
        String getName() { return getStr("name"); }
        String getAddress() { return getStr("address"); }
        double getHourlyRate() { return getDbl("hourlyRate"); }
        double getSalary() { return getDbl("salary"); }
        double getCommissionRate() { return getDbl("commissionRate"); }
        String getDate() { return getStr("date"); }
        double getHours() { return getDbl("hours"); }
        double getAmount() { return getDbl("amount"); }
        String getBank() { return getStr("bank"); }
        String getAccount() { return getStr("account"); }
    }

}